// 
//  Scanner.cs
//  
//  Author:
//       Christopher Sawyer <christoofar@gmail.com>
//  
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
// 
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.
// 
//  You should have received a copy of the GNU Lesser General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using GLib;
using Gtk;
using System.Collections.Generic;
using System.Collections.Specialized;
using Launch=Mono.SANE.SANELaunch;

namespace Mono.SANE
{
	/// <summary>
	/// Represents a scanner or other imaging device that has found to be accessible via SANE
	/// </summary>
	public class Scanner
	{
		private string _deviceid = "";
		private string _vendor = "";
		private string _model = "";
		private string _type = "";
		
		private volatile bool _started = false;
		
		private object synch = new object();
		private int _currentPage = 0;
				
		private Dictionary<string, ScannerOption> _scannerOptions = null;
		
		/// <summary>
		/// Reports progress as a page is scanned from 0%-100%
		/// </summary>
		public event ScanningProgressEvent ScanningProgress;
		/// <summary>
		/// Reports when a page has been successfully scanned with the image's file name
		/// </summary>
		public event PageScanCompletedEvent PageScanCompleted;
		/// <summary>
		/// Reports the number of pages scanned
		/// </summary>
		public event PagesScannedEvent PagesScanned;
		/// <summary>
		/// Signals that a batch scanning operation has finished.
		/// </summary>
		public event BatchScanCompletedEvent BatchScanCompleted;		
		/// <summary>
		/// Signals if SANE reports an error during scanning
		/// </summary>
		public event ScanningErrorEvent OnScanningError;
		/// <summary>
		/// Raised when the scanimage process sends a message (useful for troubleshooting)
		/// </summary>
		public event SANEMessageEvent OnSANEMessage;
		/// <summary>
		/// Reports progress as a page is scanned from 0%-100%
		/// </summary>
		public delegate void ScanningProgressEvent(double progress);
		/// <summary>
		/// Reports when a page has been successfully scanned with the image's file name
		/// </summary>
		public delegate void PageScanCompletedEvent(string imageFileName);
		/// <summary>
		/// Reports the number of pages that have been scanned
		/// </summary>
		public delegate void PagesScannedEvent(int pages);
		/// <summary>
		/// Signals that a batch scanning operation has finished.
		/// </summary>
		public delegate void BatchScanCompletedEvent();
		/// <summary>
		/// Signals if SANE reports an error during scanning
		/// </summary>
		public delegate void ScanningErrorEvent(string errorMessage);
		/// <summary>
		/// Raised when the scanimage process sends a message (useful for troubleshooting)
		/// </summary>
		public delegate void SANEMessageEvent(string message);

		
		#region Public Properties
		/// <summary>
		/// The company that manufactured this device
		/// </summary>
		public string Vendor {
			get {
				return _vendor;
			}
		}	
		
		/// <summary>
		/// The type of SANE imaging device this is
		/// </summary>
		public string Type {
			get {
				return _type;
			}
		}
		
		/// <summary>
		/// The model number of this imaging device
		/// </summary>
		public string Model {
			get {
				return _model;
			}
		}
		
		/// <summary>
		/// The SANE backend identifier for this imaging device
		/// </summary>
		public string Deviceid {
			get {
				return _deviceid;
			}
		}
		
		/// <summary>
		/// Gets the running state of the scanner
		/// </summary>
		public bool Started {
			get { return _started; }
			internal set { _started = value; }
		}
		
		/// <summary>
		/// A list of options this <see cref="Scanner"/> supports.
		/// </summary>
		public Dictionary<string, ScannerOption> Options
		{
			get 
			{
				if (_scannerOptions == null)
				{
					List<ScannerOption> options = Launch.GetScannerOptions (this._deviceid);
					_scannerOptions = new Dictionary<string, ScannerOption> ();
					foreach (ScannerOption option in options) 
					{
						_scannerOptions.Add (option.Switch, option);
					}
				}
				
				return _scannerOptions;
			}
		}
		#endregion		
	
		
		/// <summary>
		/// Scans a single picture from the scanner and formats it into a <see cref="Gdk.Pixbuf"/>
		/// </summary>
		/// <returns>
		/// A <see cref="Gdk.Pixbuf"/>
		/// </returns>
		public Gdk.Pixbuf ScanPicture ()
		{
			this.Started = false;
			return Launch.ImageToPixbuf (Launch.Crop (Launch.RawSinglePageScan (this)));
		}
		
		/// <summary>
		/// Helpful method which returns a populated Pixbuf object from an image file
		/// </summary>
		/// <param name="path">
		/// A <see cref="System.String"/>
		/// </param>
		/// <returns>
		/// A <see cref="Gdk.Pixbuf"/>
		/// </returns>
		public Gdk.Pixbuf GetPictureFromPath (string path)
		{
			System.Drawing.Image i = System.Drawing.Image.FromFile(path);
			return Launch.ImageToPixbuf (Launch.Crop (i));
		}
		
		/// <summary>
		/// Helpful method which returns a populated System.Drawing.Image object from an image file
		/// </summary>
		/// <param name="path">
		/// A <see cref="System.String"/>
		/// </param>
		/// <returns>
		/// A <see cref="System.Drawing.Image"/>
		/// </returns>
		public System.Drawing.Image GetImageFromPath (string path)
		{
			return Launch.Crop (System.Drawing.Image.FromFile(path));
		}
		
		/// <summary>
		/// Scans a single picture from the scanner and formats it into a <see cref="System.Drawing.Image"/>
		/// </summary>
		/// <returns>
		/// A <see cref="System.Drawing.Image"/>
		/// </returns>
		public System.Drawing.Image ScanImage ()
		{
			return Launch.Crop (Launch.RawSinglePageScan (this));
		}
		
		/// <summary>
		/// Starts a multi-page document scan.  This feature is only useful for equipment that
		/// has automatic document feeders (ADFs) or full-duplex scanning mode.   Use the events
		/// to detect page progress and to collect images.
		/// </summary>
		public void StartMultiPageScan ()
		{
			lock (synch)
			{
				//Reset page counter.
				_currentPage = 0;
			}
			this.Started = true;
			
			// Try to set the source to the Automatic Document Feeder
			SetADFSource ();
			
			Launch.StartMultiPageScan (this);
		}
		
		/// <summary>
		/// Attemps to set the input source to the scanner's Automatic Document Feeder, if
		/// one is available
		/// </summary>
		public void SetADFSource ()
		{
			// Find any option that looks like it might set the input tray
			foreach (ScannerOption option in Options.Values)
			{
				if (option.Switch.ToLower () == "source") 
				{
					if (!option.CurrentSetting.ToLower ().Contains ("adf") && 
						!option.CurrentSetting.ToLower ().Contains ("automatic document feeder"))
					{
						foreach (string optionswitch in option.AvailableOptions) 
						{
							string compare = optionswitch.ToLower ();
							if (compare.Contains ("adf") || (compare.Contains ("automatic document feeder"))) 
							{
								//Set the scanner to use the ADF
								option.CurrentSetting = optionswitch;
							}
						}
					}
				}
			}
		}
		
		/// <summary>
		/// Parses messages coming from scanimage (in batch mode) to raise as events on this
		/// object.
		/// </summary>
		/// <param name="message">
		/// A <see cref="System.String"/>
		/// </param>
		internal void ParseADFMessage (string message)
		{
			string[] messages;
			
			//This message is parseable
			if (message.Contains ("\n") || message.Contains ("%")) 
			{
				messages = message.Split ('\n', '%', '\r');
				foreach (string blip in messages) 
				{
					if (blip == string.Empty) 
					{
						continue;
					}
					
					// Progress event message
					if (blip.Contains ("Progress: ")) 
					{
						if (this.ScanningProgress != null)
						{
							double progress = 0.00;
							if (double.TryParse(blip.Replace("Progress: ", string.Empty), out progress))
							{
								this.ScanningProgress(progress);
							}
						}
					}
					// Page scan completed message
					else if (blip.Contains("Scanned page"))
					{
						string pageCompleted = blip.Replace("Scanned page ", string.Empty);
						pageCompleted = pageCompleted.Substring(0, pageCompleted.IndexOf("."));
						int pageComp = 0;
						
						//_currentPage is a tracking device.  It prevents us from sending
						//duplicate events for the same page.
						
						if (int.TryParse(pageCompleted, out pageComp))
						{
							if (pageComp != _currentPage)
							{
								lock(synch)
								{
									_currentPage = pageComp;
								}
								
								if (this.PageScanCompleted != null)
								{
									this.PageScanCompleted("/tmp/imgproc" + _currentPage + ".tif");	
								}
								
								if (this.PagesScanned != null)
								{
									this.PagesScanned(_currentPage);
								}
							}
						}
					} 
					// ADF ran out of paper -- job is done
					else if (blip.Contains("out of documents"))					
					{
						if (this.BatchScanCompleted != null && this.Started)
						{
							this.BatchScanCompleted();
							this.Started = false;							
						}
					} 
					// Something went awry
					else if (blip.ToLower().Contains("error"))
					{
						if (this.OnScanningError != null)
						{
							this.OnScanningError(blip);
						}
						this.Started = false;
					}
					
					if (this.OnSANEMessage != null)
					{
						this.OnSANEMessage(blip);
					}
				}
			}
		}

		internal Scanner ()
		{
		}
		
		internal Scanner (string deviceid, string vendor, string model, string type)
		{
			_deviceid = deviceid;
			_vendor = vendor;
			_model = model;
			_type = type;
		}
	}
}
